package updateinfoimpl

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	libutils "github.com/opensourceways/server-common-lib/utils"

	"cvevulner/cve-ddd/domain"
	"cvevulner/cve-ddd/domain/dp"
	"cvevulner/taskhandler"
	"cvevulner/util"
)

func (impl updateInfoImpl) UploadUpdateInfoXml(param domain.UpdateParam) (data []byte, err error) {
	var u Updates

	err = xml.Unmarshal(param.DownloadBys, &u)
	if err != nil {
		return nil, err
	}

	up := impl.updateXml(param.Sb, param.Branch)

	i := impl.numberIndex(&u, param.Sb.Identification)
	if i == -1 {
		u.Updatex = append(u.Updatex, up)
	} else {
		if up.Description == "" {
			up.Description = u.Updatex[i].Description
		}
		if up.Title == "" {
			up.Title = u.Updatex[i].Title
		}
		u.Updatex[i] = up
	}

	sort.Slice(u.Updatex, func(i, j int) bool {
		return u.Updatex[i].Id < u.Updatex[j].Id
	})

	uploadBys, err := xml.MarshalIndent(u, "", "     ")
	if err != nil {
		return nil, err
	}

	headerBytes := []byte(xml.Header)
	headerBytes = append(headerBytes, uploadBys...)

	return headerBytes, nil
}

func (impl updateInfoImpl) updateXml(sb *domain.SecurityBulletin, branch string) Update {
	var cveNums []string
	var description string
	var highestLevelIndex int
	for _, cve := range sb.Cves {
		cveNums = append(cveNums, cve.CveNum)

		subDescription := strings.ReplaceAll(cve.Description, "\n\n", "\r\n\r\n")
		subDescription = taskhandler.XmlSpecCharHand(subDescription)
		dSplit := strings.Split(subDescription, "Security Fix(es):")
		if len(dSplit) > 1 {
			if !strings.Contains(description, dSplit[0]) {
				description = dSplit[0] + "Security Fix(es):" + description
			}
			if !strings.Contains(description, dSplit[1]) {
				description += dSplit[1]
			}
		}

		// Choose the highest security level in cves, as security level in bulletin
		for k, v := range dp.SequenceSeverityLevel {
			if v == cve.SeverityLevel && k > highestLevelIndex {
				highestLevelIndex = k
			}
		}
	}
	introduction := fmt.Sprintf("An update for %s is now available for", sb.Component)

	var descr string

	title := introduction + branch

	if impl.IsCveNotice(sb.Identification) {
		if i := strings.Index(description, "Security Fix(es):"); i > 0 {
			descr = util.TrimStringNR(description[i+17:])
		}
	} else {
		descr = description
	}

	var up = Update{
		From:        "openeuler.org",
		Type:        "security",
		Status:      "stable",
		Id:          sb.Identification,
		Title:       title,
		Severity:    domain.Severity[strings.ToLower(dp.SequenceSeverityLevel[highestLevelIndex])],
		Release:     "openEuler",
		Issued:      &Issued{Date: sb.Date},
		Description: descr,
	}

	var ref []Reference
	for _, s := range cveNums {
		ref = append(ref, Reference{
			Href:  domain.CveUrlPrefix + s,
			Id:    s,
			Title: s,
			Type:  "cve",
		})
	}

	up.References = &References{Reference: ref}

	var pack []Package
	for arch, pl := range sb.ProductTree {
		if arch == "src" {
			continue
		}

		for _, productPackage := range pl {
			var pe Package
			pe.Filename = productPackage.FullName
			packVersionList := strings.Split(productPackage.FullName, "-")
			if len(packVersionList) >= 3 {
				pe.Version = packVersionList[len(packVersionList)-2]
				rpmName := packVersionList[len(packVersionList)-1][:len(packVersionList[len(packVersionList)-1])-4]
				lastIndex := strings.LastIndexAny(rpmName, ".")
				if lastIndex != -1 {
					pe.Release = rpmName[:lastIndex]
					pe.Arch = rpmName[lastIndex+1:]
				}
				pe.Name = strings.Join(packVersionList[0:len(packVersionList)-2], "-")
			}

			if !strings.Contains(pe.Filename, "kernel") {
				epoch, err := impl.findEpoch(domain.ScriptPath, branch, pe.Filename, pe.Arch, 1)
				if err == nil && len(epoch) > 0 {
					pe.Epoch = string(epoch)
				}
			}

			pack = append(pack, pe)
		}
	}

	up.Pkglist = &Pkglist{Collection: &Collection{Name: "openEuler", Package: pack}}

	return up
}

func (impl updateInfoImpl) numberIndex(u *Updates, securityNumber string) (index int) {
	index = -1
	for k, v := range u.Updatex {
		if strings.EqualFold(v.Id, securityNumber) {
			index = k
			return
		}
	}

	return
}

func (impl updateInfoImpl) IsCveNotice(securityNoticeNo string) bool {
	return impl.GenNoticeType(securityNoticeNo) == domain.NoticeTypeCVE
}

func (impl updateInfoImpl) GenNoticeType(securityNoticeNo string) string {
	if strings.Contains(securityNoticeNo, "BA") {
		return domain.NoticeTypeBug
	}

	if strings.Contains(securityNoticeNo, "HotPatchSA") {
		return domain.NoticeTypeCVE
	}

	if strings.Contains(securityNoticeNo, "SA") {
		return domain.NoticeTypeCVE
	}

	return ""
}

func (impl updateInfoImpl) findEpoch(script, branch, filename, arch string, i int) ([]byte, error) {
	var archs = []string{arch}
	if arch == "noarch" {
		archs = []string{"aarch64", "x86_64"}
	}
	for _, a := range archs {
		epoch, err, _ := libutils.RunCmd(
			script,
			filepath.Join("/opt/app/", branch, strconv.Itoa(i), time.Now().Format("150405.999")),
			fmt.Sprintf(domain.PkgUrl, branch, a, filename),
		)

		if err != nil {
			return nil, fmt.Errorf("failed to get epoch, pkgUrl is %s", fmt.Sprintf(domain.PkgUrl, branch, a, filename))
		}

		if err == nil {
			if strings.Contains(string(epoch), "404") || strings.Contains(string(epoch), "502") {
				continue
			}
			if ix := bytes.Index(epoch, []byte("NOKEY")); ix > 0 {
				epoch = bytes.TrimSpace(epoch[ix+5:])
			} else {
				epoch = bytes.TrimSpace(epoch)
			}

			return domain.Num.Find(epoch), nil
		}
	}

	return nil, nil
}
